/*
  esp8266-fan project.
  same code use pubsubclient lib.
  this is  a esp8266 project, in this project Reads a tempreture from ds18b20.control fan using relay. sync data to iot server by mqtt.
  sw version:0.9.1
  create date:2023-4-2
  create by:xu.
*/

// Include the libraries we need
#include <OneWire.h>
#include <DallasTemperature.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h>


#define DEBUG 0
//change to your wifi and password
#define HOME_WIFI_AP "xxxxx"
#define HOME_WIFI_PASSWORD "xxxxxxx"

/*pin map*/
/*
-----esp8266pin-----nodemcu------func-------
        IO4           D2          ds18b20 Dq
        IO5           D1          Wifi switch
        IO12          D6          low power   ctrl
        IO13          D7          wifi led
        IO14          D5          relay




*/

// digital pin 5 has a wifiBtn attached to it. Give it a name:
int wifiBtn = D1;
int pwrCtrl = D6;
int relayCtrl = D5;

/*ds18b20*/
#define TEM_DQ_PIN D2
// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
OneWire oneWire(TEM_DQ_PIN);
// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);
// arrays to hold device address
DeviceAddress Tem_Addr;

float tem_val = 0;

float Max_tem = 27.0;
int Fan_flag=0;

/*mqtt broker*/
char MqttServer[] = "xxxxxxxx";

String WIFI_APN, WIFI_PASSWORD;

const char* pubTopic = "n2/devices/esp-fan";
// 初始化以太网客户端对象 -- ESP8266WiFi.h
WiFiClient espClient;
// 初始化MQTT库PubSubClient.h的对象
PubSubClient client(espClient);

const String macAddress = WiFi.macAddress();
const char* clientID = macAddress.c_str();


int status = WL_IDLE_STATUS;
unsigned long lastSend;
unsigned long lostTime;
unsigned long flashTime;

#define NET_LED D7
//#define MAX_LOST_TIME 5*60*1000 //5min
#define MAX_LOST_TIME 80000  //80s

int btnState = 0;
// the setup routine runs once when you press reset:



void setup() {
  // initialize serial communication at 9600 bits per second:
  Serial.begin(115200);
  Serial.println("\nproj:esp-fan v1.0.0.");
  // make the wifiBtn's pin an input:
  pinMode(wifiBtn, INPUT_PULLUP);
  pinMode(pwrCtrl, OUTPUT);
  pinMode(relayCtrl, OUTPUT);
  pinMode(NET_LED, OUTPUT);  
  pinMode(BUILTIN_LED, OUTPUT);  

  //ini
  digitalWrite(pwrCtrl, LOW);
  digitalWrite(relayCtrl, LOW);
  digitalWrite(NET_LED, HIGH);  // Turn the LED off (Note that LOW is the voltage level

  //wifi
  WIFI_APN = HOME_WIFI_AP;
  WIFI_PASSWORD = HOME_WIFI_PASSWORD;
  Serial.println("init wifi.");
  Serial.print("wifi cfg:");
  Serial.print(WIFI_APN);
  Serial.print("+");
  Serial.println(WIFI_PASSWORD);
  InitWiFi();

  // 连接WiFi
  WiFi.begin(WIFI_APN, WIFI_PASSWORD);
  client.setServer(MqttServer, 1883);  // 连接WiFi之后，连接MQTT服务器
  client.setCallback(callback);
  client.connect("greenhouse0002", "test", "123");
  if (client.connected()) {
    client.subscribe("inTopic");
  }
  lastSend = 0;
  lostTime = 0;
  flashTime = 0;

  //ds1820
  Serial.println("init ds18b20.");
  iniDs18b20(Tem_Addr);
  Serial.print("Ds18b20 Addr:");
  printAddress(Tem_Addr);
}

// the loop routine runs over and over again forever:
void loop() {
  //check wifi status.
  status = WiFi.status();
  if (status != WL_CONNECTED) {
    digitalWrite(BUILTIN_LED, HIGH);  // Turn the LED off (Note that LOW is the voltage level

    while (status != WL_CONNECTED) {
      Serial.print("[loop()]Attempting to connect to WPA SSID: ");
      Serial.println(WIFI_APN);
      // 连接WiFi热点
      status = WiFi.status();
      delay(500);
      flashLed();
    }
    digitalWrite(BUILTIN_LED, LOW);  // Turn the LED on (Note that LOW is the voltage level

    Serial.println("[loop()]Connected to AP");
  }
  if (!client.connected()) {

    if (millis() - lastSend > 5000) {
      reconnect();
      lastSend = millis();
    }
  }



  if (client.connected()) {
    if (millis() - lastSend > 60000) {  // 用于定时1/2min发送一次数据
      //send data.
      Serial.println("[MQTT]updatedata.");
      getAndSendTemperatureAndHumidityData();  // 获取温湿度数据发送到MQTT服务器上去



      lastSend = millis();
      //update lost time
      lostTime = millis();
    }
  }

  //for (int i=0; i<5000; i++) {
  client.loop();
  // delay(1000);
  //}
  //update tempreture.
  if (millis() - flashTime > 1000) {
    iniDs18b20(Tem_Addr);
    tem_val = getTemperature(Tem_Addr);
    Serial.print("tem:");
    Serial.println(tem_val);
    flashNetLed();
    //Serial.print("lost time:");
   // Serial.println(millis() - lostTime);
    flashTime = millis();
  }
  // read the input pin:
  btnState = digitalRead(wifiBtn);
  // print out the state of the button:
  //  Serial.println(btnState);
  if (btnState == 0) {
    //wifiBtn press
    Serial.println("wifi btn press.");
    //change pwr & relay
    //digitalWrite(pwrCtrl, LOW);
   // digitalWrite(relayCtrl, HIGH);

    //get tem
   // tem_val = getTemperature(Tem_Addr);
   // Serial.print("tem:");
   // Serial.print(tem_val);
   // Serial.println();
  } else {

    //digitalWrite(pwrCtrl, HIGH);
    //digitalWrite(relayCtrl, LOW);
  }

  //check tempreture
  if(tem_val > Max_tem){
    digitalWrite(relayCtrl, HIGH);
    Fan_flag=1;
  }else
  {
    digitalWrite(relayCtrl, LOW);
    Fan_flag=0;
  }
  delay(200);  // delay in between reads for stability
  Serial.print(".");
}

uint8_t iniDs18b20(DeviceAddress& addr) {
  int count = 0;
  // start serial port
  if (DEBUG) Serial.begin(115200);

  // locate devices on the bus
  sensors.begin();
  count = sensors.getDeviceCount();
  if (DEBUG) {
    Serial.print("Found ");
    Serial.print(count, DEC);
    Serial.println(" devices.");
  }




  if (!sensors.getAddress(addr, 0)) {

    if (DEBUG) Serial.println("Unable to find address for Device 0");
    return 0;
  } else {
    if (DEBUG) {
      Serial.print("Device 0 Address: ");
      printAddress(addr);
      Serial.println();
    }
    // set the resolution to 9 bit (Each Dallas/Maxim device is capable of several different resolutions)
    sensors.setResolution(addr, 9);
  }
  return count;
}
// function to print a device address
void printAddress(DeviceAddress deviceAddress) {
  for (uint8_t i = 0; i < 8; i++) {
    if (deviceAddress[i] < 16) Serial.print("0");
    Serial.print(deviceAddress[i], HEX);
  }
  Serial.println();
}

float getTemperature(DeviceAddress& addr) {
  if (DEBUG) Serial.print("Requesting temperatures...");
  sensors.requestTemperatures();  // Send the command to get temperatures
  if (DEBUG) Serial.println("DONE");

  float tempC = sensors.getTempC(addr);
  if (DEBUG) Serial.print("Temp C: ");
  if (DEBUG) Serial.print(tempC);
  Serial.println();
  return tempC;
}

void InitWiFi() {
  WiFi.begin(WIFI_APN, WIFI_PASSWORD);
  // 检测WiFi模块在不在，宏定义：WL_NO_SHIELD = 255,WL_IDLE_STATUS = 0,
  //if (WiFi.status() == WL_NO_SHIELD) {
  //  Serial.println("WiFi shield not present");
  //  while (true);
  // }

  Serial.println("[InitWiFi]Connecting to AP ...");
  // 尝试连接WiFi网络
  while (status != WL_CONNECTED) {
    Serial.print("[InitWiFi]Attempting to connect to WPA SSID: ");
    Serial.println(WIFI_APN);
    // Connect to WPA/WPA2 network
    status = WiFi.status();
    delay(500);
    flashLed();
    Serial.print(".");
  }
  Serial.println("[InitWiFi]Connected to AP");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  digitalWrite(BUILTIN_LED, LOW);  // Turn the LED on (Note that LOW is the voltage level
}

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();

  // Switch on the LED if an 1 was received as first character
  if ((char)payload[0] == '1') {
    digitalWrite(BUILTIN_LED, LOW);  // Turn the LED on (Note that LOW is the voltage level
    // but actually the LED is on; this is because
    // it is active low on the ESP-01)
  } else {
    digitalWrite(BUILTIN_LED, HIGH);  // Turn the LED off by making the voltage HIGH
  }
}

/**
      *
      * MQTT客户端断线重连函数
      *
      */

void reconnect() {
  // 一直循环直到连接上MQTT服务器
  if (!client.connected()) {
    Serial.print("[reconnect]Connecting to MQTT Server ...");
    // 尝试连接connect是个重载函数 (clientId, username, password)
    String id = "greenhouse0001" + millis();
    if (client.connect(id.c_str(), "test", "123")) {
      Serial.println("[DONE]");
      client.subscribe("inTopic");
    } else {
      Serial.print("[FAILED] [ mqtt connect error code = ");
      Serial.print(client.state());
      Serial.println(" : retrying in 5 seconds]");  // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}


  /*
      *
      * 读取温湿度数据，然后发送到MQTT服务器上去
      *
      */
     void getAndSendTemperatureAndHumidityData()
     {
       Serial.println("Collecting temperature data.");
       
       // 大概250ms读取一次
       //float h = millis()%30;
       float h = 0;
       float t = tem_val;
       int flag = Fan_flag;
       long time = millis()/1000;


       // 查看是否读取温湿度失败的
       if (isnan(h) || isnan(t)) {
         Serial.println("Failed to read from  sensor!");
         return;
       }

       Serial.print("Humidity: ");
       Serial.print(h);
       Serial.print(" %\t");
       Serial.print("Temperature: ");
       Serial.print(t);
       Serial.print("Fan state: ");
       Serial.print(flag);
       Serial.print("time: ");
       Serial.print(time);

       String temperature = String(t);
       String humidity = String(h);
       String fan_flg = String(flag);

       // Just debug messages
       Serial.print( "Sending temperature and humidity : [" );
       Serial.print( temperature ); Serial.print( "," );
       Serial.print( humidity );Serial.print( "," );
       Serial.print( fan_flg );Serial.print( "," );
       Serial.print( time );
       Serial.print( "]   -> " );

       // 构建一个 JSON 格式的payload的字符串
       String payload = "{";
       payload += "\"temperature\":"; payload += temperature; payload += ",";
       payload += "\"humidity\":"; payload += humidity;payload += ",";
       payload += "\"fan\":"; payload += fan_flg;payload += ",";
       payload += "\"time\":"; payload += time;
       payload += "}";

       // Send payload
       char attributes[100];
       payload.toCharArray( attributes, 100 );

       // boolean publish(const char* topic, const char* payload);
      
      char ret = client.publish( pubTopic, attributes );
       Serial.print("[publish]-->>");
       Serial.print(pubTopic);
       Serial.println( attributes );
              
              
       Serial.print( "publish" );
       Serial.println( ret );


       
     }




void flashNetLed() {
  //check lostTime if > MAX_LOST_TIME flash
  if (millis() - lostTime > MAX_LOST_TIME) {
    //net lost
  } else {
    //net connect
    flashLed();
  }
}

void flashLed() {
  digitalWrite(NET_LED, HIGH);
  delay(300);
  digitalWrite(NET_LED, LOW);
  delay(300);
}
